package mn.idax.exchange.otc.util;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.model.*;
import mn.idax.exchange.auth.util.UUidUtils;
import mn.idax.exchange.otc.config.AliyunOSSConfig;
import mn.idax.exchange.otc.enums.response.AliyunOSSResponseEnum;
import mn.idax.exchange.otc.enums.response.UploadFileSizeEnum;
import mn.idax.exchange.otc.exception.IdaxException;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.util.FileUtil;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Auther: dison
 * @Date: 2018/8/7 16:24
 * @Description: 文件工具类
 */
public class AliyunOSSUtils {

    public static void main(String[] args) {
        String fileName = "123.png";
        String fullFileName = getFullFileName(fileName);
        System.out.println(getImageKey(fullFileName));
    }
    /**
     *
     * 功能描述:
     *         获取阿里云上传文件的key
     * @param: fileName
     * @return:
     * @auther: dison
     * @date: 2018/8/7 17:03
     */
    public static String getOSSKey(String fileName){
        long start = System.currentTimeMillis();

        String ext = getFileExt(fileName);
        String ossKey = UUidUtils.getUUID() + "." + ext;

        long end = System.currentTimeMillis();
        long cost = end - start;
        LogUtils.info(AliyunOSSUtils.class,"AliyunUpload getOSSKey cost " + cost);
        return ossKey;
    }

    /**
     *
     * 功能描述: 
     *      获取文件后缀扩展名称
     * @param: fileName 文件名
     * @return: 
     * @auther: dison
     * @date: 2018/8/8 10:21
     */
    public static String getFileExt(String fileName){
        if (!fileName.contains(".")){
            return null;
        }
        int lastPeriodIndex = fileName.lastIndexOf(".");
        String ext = fileName.substring(lastPeriodIndex + 1).toLowerCase();
        return ext;
    }

    /**
     *
     * 功能描述:
     *          上传文件类型交易
     * @param: fileName 文件名称
     * @return:
     * @auther: dison
     * @date: 2018/8/8 11:14
     */
    public static boolean verifyUploadFileType(String fileName){
        String ext = getFileExt(fileName);
        if (StringUtils.isNotEmpty(ext)){
            UploadFileSizeEnum uploadFileSizeEnum = UploadFileSizeEnum.getValue(ext);
            if (uploadFileSizeEnum != null){
                return true;
            }
        }
        return false;
    }

    /**
     *
     * 功能描述:
     *      批量校验文件类型
     * @param: files 文件列表
     * @return:
     * @auther: dison
     * @date: 2018/8/8 11:16
     */
    public static boolean batchVerifyUploadFileType(MultipartFile[] files){
        for (MultipartFile file : files) {
            if (!verifyUploadFileType(file.getOriginalFilename())){
                return false;
            }
        }
        return true;
    }

    /**
     *
     * 功能描述:
     *      验证上传文件大小
     * @param: file 上传文件
     * @return:
     * @auther: dison
     * @date: 2018/8/8 10:32
     */
    public static boolean verifyFileSize(MultipartFile file){
        String ext = getFileExt(file.getOriginalFilename());
        if (StringUtils.isNotEmpty(ext)){
            UploadFileSizeEnum uploadFileSizeEnum = UploadFileSizeEnum.getValue(ext);
            BigDecimal fileSize = getFileSize(file);

            if  (fileSize.compareTo(new BigDecimal(uploadFileSizeEnum.getMinSize())) <= BigDecimal.ZERO.intValue()){
                return false;
            }else if  (fileSize.compareTo(new BigDecimal(uploadFileSizeEnum.getMaxSize())) > BigDecimal.ZERO.intValue()){
                return false;
            }
        }
        return true;
    }

    /**
     *
     * 功能描述:
     *      批量校验上传文件
     * @see #verifyFileSize(MultipartFile)
     * @param: files
     * @return:
     * @auther: dison
     * @date: 2018/8/8 10:55
     */
    public static boolean batchVerifyFileSize(MultipartFile[] files){
        for (MultipartFile file : files) {
            if (!verifyFileSize(file)){
                return false;
            }
        }
        return true;
    }

    /**
     *
     * 功能描述:
     *      获取文件大小（KB）
     * @param: file 上传的文件
     * @return:
     * @auther: dison
     * @date: 2018/8/8 10:28
     */
    public static BigDecimal getFileSize(MultipartFile file){
        return new BigDecimal(file.getSize());
    }

    /**
     *
     * 功能描述:
     *      初始化阿里云配置信息
     * @param: ossConfig 阿里云配置信息
     * @return:
     * @auther: dison
     * @date: 2018/8/7 17:04
     */
    public static OSSClient getOSSClient(AliyunOSSConfig ossConfig){

        long start = System.currentTimeMillis();

        OSSClient ossClient = new OSSClient(ossConfig.getEndpoint(),
                ossConfig.getAccessKeyId(),
                ossConfig.getAccessKeySecret());

        long end = System.currentTimeMillis();
        long cost = end - start;
        LogUtils.info(AliyunOSSUtils.class,"AliyunUpload getOSSClient cost " + cost);
        return ossClient;
    }


    /**
     *
     * 功能描述:
     *      上传流文件
     * @param: ossClient
     * @param: ossConfig 阿里云OSS配置信息
     * @param: fileName 文件名称
     * @param: inputStream 文件流
     * @return:
     * @auther: dison
     * @date: 2018/8/7 17:04
     */
    public static String uploadInputStream(OSSClient ossClient, AliyunOSSConfig ossConfig, String fileName, InputStream inputStream){
        try {
            String fullFileName = getFullFileName(fileName);
            long start = System.currentTimeMillis();

            PutObjectResult putObjectResult = ossClient.putObject(ossConfig.getBucketName(),fullFileName,inputStream);

            long end = System.currentTimeMillis();
            long cost = end - start;
            LogUtils.info(AliyunOSSUtils.class,"AliyunUpload ossClientPutObject cost " + cost);
            if (putObjectResult != null){
                return getUploadPath(ossConfig,fullFileName);
            }
        }catch (Exception e){
            LogUtils.error(AliyunOSSUtils.class,"AliyunOssUpload InputStream Error",e);
        }finally {
            ossClient.shutdown();
        }
        return null;
    }

    /**
     *
     * 功能描述:
     *         上传文件
     * @param: ossClient
     * @param: ossConfig 阿里云OSS配置信息
     * @param: fileName 文件名称
     * @param: file 文件
     * @return:
     * @auther: dison
     * @date: 2018/8/7 17:04
     */
    public static String uploadFile(OSSClient ossClient, AliyunOSSConfig ossConfig, String fileName, File file){
        try {
            String fullFileName = getFullFileName(fileName);
            PutObjectResult putObjectResult = ossClient.putObject(ossConfig.getBucketName(),fullFileName,file);
            if (putObjectResult != null){
                return getUploadPath(ossConfig,fullFileName);
            }
        }catch (Exception e){
            LogUtils.error(AliyunOSSUtils.class,"AliyunOssUpload File Error",e);
        }finally {
            ossClient.shutdown();
        }
        return null;
    }


    public static List<UploadPartRequest> getUploadPartRequests(AliyunOSSConfig ossConfig, String uploadId, String fullFileName,File file) throws FileNotFoundException,IOException{
        /* 步骤2：上传分片。
         */
        // 计算文件有多少个分片。
        final long partSize = 2 * 1024 * 1024L;   // 2MB
        long fileLength = file.length();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }

        List<UploadPartRequest> listPartRequest = new ArrayList<>();
        // 遍历分片上传。
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
            InputStream instream = new FileInputStream(file);
            // 跳过已经上传的分片。
            instream.skip(startPos);
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(ossConfig.getBucketName());
            uploadPartRequest.setKey(fullFileName);
            uploadPartRequest.setUploadId(uploadId);
            uploadPartRequest.setInputStream(instream);
            // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
            uploadPartRequest.setPartSize(curPartSize);
            // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
            uploadPartRequest.setPartNumber( i + 1);
            listPartRequest.add(uploadPartRequest);
        }

        return listPartRequest;
    }

    /**
     * IO密集型任务  = 一般为2*CPU核心数（常出现于线程中：数据库数据交互、文件上传下载、网络数据传输等等）
     * CPU密集型任务 = 一般为CPU核心数+1（常出现于线程中：复杂算法）
     * 混合型任务  = 视机器配置和复杂度自测而定
     */
    private static int corePoolSize = Runtime.getRuntime().availableProcessors();

    private static ThreadPoolExecutor executor  = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(1000));

    /**
     *
     * 功能描述:
     *          异步分片上传，暂补可用
     * @param:
     * @return:
     * @auther: dison
     * @date: 2018/8/23 13:39
     */
    @Deprecated
    public static String partAyncUploadInputStream(OSSClient ossClient, AliyunOSSConfig ossConfig, String fileName,File file){
        String fullFileName = getFullFileName(fileName);
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(ossConfig.getBucketName(), fullFileName);
        InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
        try{

            List<PartETag> partETags =  new ArrayList<PartETag>();
            // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
            String uploadId = result.getUploadId();
            List<UploadPartRequest> listPartRequest = getUploadPartRequests(ossConfig,uploadId,fullFileName,file);

            CountDownLatch latch = new CountDownLatch(5);
            listPartRequest.forEach(partRequest ->{
                executor.execute(() ->{
                    UploadPartResult uploadPartResult = ossClient.uploadPart(partRequest);
                    // 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
                    partETags.add(uploadPartResult.getPartETag());
                    latch.countDown();
                });
            });
            latch.await();

            // 在执行该操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(ossConfig.getBucketName(), fullFileName, uploadId, partETags);
            CompleteMultipartUploadResult uploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);

            if (uploadResult != null){
                return getUploadPath(ossConfig,fullFileName);
            }
        }catch (Exception e){
            LogUtils.error(AliyunOSSUtils.class,"AliyunOssPartUploadAsync Error",e);
        }finally {
            // 关闭OSSClient。
            ossClient.shutdown();
        }
        return null;
    }
    public static String partUploadInputStream(OSSClient ossClient, AliyunOSSConfig ossConfig, String fileName, Long fileLength,File file){

        CountDownLatch latch = new CountDownLatch(5);

        /* 步骤1：初始化一个分片上传事件。
         */
        String fullFileName = getFullFileName(fileName);
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(ossConfig.getBucketName(), fullFileName);
        InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
        // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
        String uploadId = result.getUploadId();
        /* 步骤2：上传分片。
         */
        // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
        List<PartETag> partETags =  new ArrayList<PartETag>();
        // 计算文件有多少个分片。
        final long partSize = 2 * 1024 * 1024L;   // 1MB
//        final long partSize = 10 * 1024L;   // 1MB
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }

        try{
            // 遍历分片上传。
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                InputStream instream = new FileInputStream(file);
                // 跳过已经上传的分片。
                instream.skip(startPos);
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(ossConfig.getBucketName());
                uploadPartRequest.setKey(fullFileName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
                uploadPartRequest.setPartNumber( i + 1);
                LogUtils.info(AliyunOSSUtils.class,"============1==========>>总分片数["+partCount+"] ->当前分片数["+ uploadPartRequest.getPartNumber() + "] ->日期["+ DateUtils.getFormate(new Date())+"]");
                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                LogUtils.error(AliyunOSSUtils.class,"============2==========>>");
                // 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
                partETags.add(uploadPartResult.getPartETag());
            }
            /* 步骤3：完成分片上传。
             */
            // 排序。partETags必须按分片号升序排列。
            Collections.sort(partETags, new Comparator<PartETag>() {
                @Override
                public int compare(PartETag p1, PartETag p2) {
                    return p1.getPartNumber() - p2.getPartNumber();
                }
            });
            // 在执行该操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(ossConfig.getBucketName(), fullFileName, uploadId, partETags);
            CompleteMultipartUploadResult uploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);

            if (uploadResult != null){
                return getUploadPath(ossConfig,fullFileName);
            }
        }catch (Exception e){
            LogUtils.error(AliyunOSSUtils.class,"AliyunOssPartUpload Error",e);
        }finally {
            // 关闭OSSClient。
            ossClient.shutdown();
        }

        return null;
    }

    /**
     *
     * 功能描述:
     *      判断文件是否存在
     * @param: ossConfig 阿里云OSS配置信息
     * @param: fileName 文件名称
     * @return:
     * @auther: dison
     * @date: 2018/8/8 11:09
     */
    public static boolean doesObjectExist(AliyunOSSConfig ossConfig,String fileName){
        OSSClient ossClient = getOSSClient(ossConfig);
        try {
            return ossClient.doesObjectExist(ossConfig.getBucketName(),fileName);
        }catch (Exception e){
            LogUtils.error(AliyunOSSUtils.class,"AliyunOssDoesObjectExist Error",e);
        }finally {
            ossClient.shutdown();
        }
        return false;
    }

    /**
     *
     * 功能描述:
     *      获取上传地址
     * @param: ossConfig 阿里云OSS配置信息
     * @param: fullFileName 上传的完整路径
     * @return:
     * @auther: dison
     * @date: 2018/8/7 17:04
     */
    public static String getUploadPath(AliyunOSSConfig ossConfig,String fullFileName){

        long start = System.currentTimeMillis();

        StringBuffer uploadPath = new StringBuffer();
        uploadPath.append("http://");
        uploadPath.append(ossConfig.getBucketName()).append(".");
        uploadPath.append(ossConfig.getEndpoint()).append("/");
        uploadPath.append(fullFileName);

        long end = System.currentTimeMillis();
        long cost = end - start;
        LogUtils.info(AliyunOSSUtils.class,"AliyunUpload getUploadPath cost " + cost);


        return uploadPath.toString();
    }

    /**
     *
     * 功能描述:
     *      设置防盗链信息（还可设置资源共享https://help.aliyun.com/document_detail/32018.html?spm=a2c4g.11186623.6.714.ihycke）
     * @param: ossConfig 配置信息
     * @ossClient: ossClient
     * @return:
     * @auther: dison
     * @date: 2018/8/7 9:36
     */
    public static void stepReferer(AliyunOSSConfig ossConfig, OSSClient ossClient) {
        if (Boolean.valueOf(ossConfig.getReferersSwitch()) && StringUtils.isNotEmpty(ossConfig.getReferers())) {
            String[] referers = ossConfig.getReferers().split(",");
            List<String> refererList = new ArrayList<String>();
            for (String referer : referers) {
                refererList.add(referer);
            }
            // 设置存储空间Referer列表。设为true表示Referer字段允许为空。
            BucketReferer br = new BucketReferer(true, refererList);
            ossClient.setBucketReferer(ossConfig.getBucketName(), br);
        }
    }

    public static String getImageKey(String aliyunPath){
        long start = System.currentTimeMillis();

         String imageKey = aliyunPath.substring(aliyunPath.lastIndexOf("/"), aliyunPath.length());
         imageKey = imageKey.substring(1,imageKey.lastIndexOf("."));

        long end = System.currentTimeMillis();
        long cost = end - start;
        LogUtils.info(AliyunOSSUtils.class,"AliyunUpload getImageKey cost " + cost);
         return imageKey;
    }

    /**
     *
     * 功能描述:
     *      获取完整的文件地址(日期【年月日】 + / + UUID + .+ ext)
     * @param: 上传文件名称
     * @return:
     * @auther: dison
     * @date: 2018/8/7 17:05
     */
    public static String getFullFileName(String fileName){
        long start = System.currentTimeMillis();

        String fullFileName = getFileFolderName() + "/" + getOSSKey(fileName);

        long end = System.currentTimeMillis();
        long cost = end - start;
        LogUtils.info(AliyunOSSUtils.class,"AliyunUpload getFullFileName cost " + cost);
        return fullFileName;
    }

    /**
     *
     * 功能描述:
     *      获取文件夹【年月日】
     * @param:
     * @return:
     * @auther: dison
     * @date: 2018/8/7 17:05
     */
    public static String getFileFolderName(){
        return DateUtils.getDateToStr(new Date(),"yyyyMMdd");
    }

    /**
     *
     * 功能描述:
     *      批量校验上传文件
     * @param:
     * @return:
     * @auther: dison
     * @date: 2018/8/14 12:25
     */
    public static void verifyMultipartFiles(MultipartFile[] files) throws IdaxException {
        long start = System.currentTimeMillis();
        if (null == files || files.length <= BigDecimal.ZERO.intValue()){
            throw new IdaxException(AliyunOSSResponseEnum.UPLOAD_NOT_FUND.getCode());
        }else if (!AliyunOSSUtils.batchVerifyUploadFileType(files)){
            throw new IdaxException(AliyunOSSResponseEnum.UPLOAD_FILE_TYPE_ILLEGAL  .getCode());
        }else if (!AliyunOSSUtils.batchVerifyFileSize(files)){
            throw new IdaxException(AliyunOSSResponseEnum.UPLOAD_FILE_SIZE_ILLEGAL.getCode());
        }
        long end = System.currentTimeMillis();
        long cost = end - start;
        LogUtils.info(AliyunOSSUtils.class,"AliyunUpload verifyMultipartFiles cost " + cost);
    }


}
